//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<XDemoApp.cpp>
///	@path	~/src/xdemo/
///	@date	2007/10/17
///	@desc	.

#define _FULLAPI
#include "config/config.h"

#include "lib/system/sysinfo.h"

#include "database/DataManager.h"
#include "database/xres/CLocalFileSystem.h"
#include "database/xres/CXmlFileHandler.h"
#include "database/xdata/CDirectoryData.h"
#include "database/xdata/CTreeData.h"

#include "graphics/DxMath.h"
#include "graphics/dx9/xeres_dx9.h"
#include "graphics/dx9/import_dx9.h"
#include "graphics/dx10/xeres_dx10.h"
#include "graphics/dx10/import_dx10.h"
#include "graphics/IRenderer.h"
#include "graphics/canvas/CCanvasProxy.h"

#include "scene/IScene.h"
#include "scene/camera/CCameraBase.h"
#include "scene/xsimple/XSimple.h"

#include "engine/xcore/xeres_engine.h"
#include "engine/xcore/event_center.h"
#include "engine/xcore/CInputEvent.h"

#include "python/python_plug.h"
#include "python/interpreter/CPythonInterpreter.h"
#include "python/wrap/expose_python.h"

#include "lua/lua_plug.h"
#include "lua/interpreter/CLuaInterpreter.h"
#include "lua/wrap/expose_lua.h"

#include "gui/xgui/XCtrls.h"
#include "gui/xgui/XCtrlsExt.h"
#include "gui/xgui/XEditCtrl.h"
#include "gui/xgui/XTagLayout.h"

#include "appear/xwin/DefaultInputAdaptor.h"
#include "appear/xwin/DirectInputAdapter.h"
#include "appear/xwin/RegularGameMessage.h"
#include "appear/xwin/InputReflection.h"
#include "appear/xwin/ShellConsole.h"
#include "appear/xwin/OutlineConsole.h"
#include "appear/xwin/ProfileConsole.h"

#include "xdemo/XDemoApp.h"

namespace xeres {

	// ctor
	XDemoApp::XDemoApp( HWND hWnd )
		: m_hWnd( hWnd )
		, m_inputTicker( NULL )
		, m_inputHandler( NULL )
		, m_inputDispatcher( NULL )
		, m_inputFilter( NULL )
		, m_regularFilter( NULL )
		, m_debugger( NULL )
	{
	}

	// dtor
	XDemoApp::~XDemoApp( void )
	{
	}

	// This helper class encapsulates a bundle of callbacks.
	class XDemoInitHelper
	{
	public:
		
		// Callback helpers

		// This callback registers extension classes.
		static void _xdemo_init_reg_class( void )
		{
			TRACE_INFO( _S(".Init: Registering class...") );
			IOBJECT_REGISTER_CLASS( XSimple );
		}

		// This callback initializes data system for input/output.
		static void _xdemo_init_filesystem( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Starting file system...") );

			// Dispatch to different file system (file or package).

			// Configuration file system.
			Ref<CLocalFileSystem> etc_filesystem = new CLocalFileSystem;
			etc_filesystem->SetPathBase( _S("/etc/") );
			Ref<CDirectoryData> etc_root = new CDirectoryData( etc_filesystem , _S("/") , _S("/etc/") );

			// Add root directory to engine.
			app->engine().AddDataEntryRoot( etc_root );


			// Resource file system.
			Ref<CLocalFileSystem> res_filesystem = new CLocalFileSystem;
			res_filesystem->SetPathBase( _S("/res/") );
			Ref<CDirectoryData> res_root = new CDirectoryData( res_filesystem , _S("/") , _S("/res/") );
			
			// Add root directory to engine.
			app->engine().AddDataEntryRoot( res_root );

			// Create XML file handler.
			Ref<CXmlFileHandler> xml = new CXmlFileHandler;

			// Register file handler.
			res_filesystem->AddFileHandler( _S("xml") , xml );
			etc_filesystem->AddFileHandler( _S("xml") , xml );

			// Extension for Scene loaders in resources.
			res_filesystem->AddFileHandler( _S("xsimple;xmodel") , xml );

			// Save file system.
			app->m_etc = etc_filesystem;
			app->m_res = res_filesystem;
		}

		// This callback initializes graphics device.
		// We use the class we just registered to create rendering environment.
		static void _xdemo_init_device( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Initializing device...") );

			Ref<IData> entry = app->engine().OpenData( _S("etc/engine_config.xml") );

			int version = 9;
			if( entry.IsValid() )
			{
				version = entry->ReadInt( _S("device/version" ) , version );
			}

			bool enable_debug = false;
#ifndef _FINAL
			if( entry.IsValid() )
			{
				enable_debug = entry->ReadBool( _S("device/debug" ) , enable_debug );
			}
#endif

			// Register DX9 classes.
			LoadDirectX9( enable_debug );

			if( version > 9 && IS_OS_VISTA() )
			{
				// Register DX10 classes.
				LoadDirectX10( enable_debug );
				app->engine().ConfigDevice( app->m_hWnd , _S("CDevice10") , _S("CRenderer10") , entry , entry );
			}
			else
			{
				// Create new graphics device by passing class name.
				app->engine().ConfigDevice( app->m_hWnd , _S("CDevice9") , _S("CRenderer9") , entry , entry );
			}

			// Set default shading mode.
			app->engine().ChangeShadingMode( _S("forward") );

			// Config shader include directory.
			app->engine().GetRenderer()->AddInclude( app->m_res , _S("fx/include") );

			// Register key map.
			app->m_hotkey.SetKey( Input::INPUT_STATE_ALT , KeyInput::KEY_RETURN ,
				bind( &IDevice::ToggleFullScreen , &(*(app->engine().GetDevice())) , app->m_hWnd ) );
		}

		// This call back initializes scene manager.
		static void _xdemo_init_scene_mgr( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Constructing scene...") );

			Ref<IData> entry = app->engine().OpenData( _S("res/scene/scene_config.xml") );
			// Initializes scene manager.
			app->engine().ConfigScene( entry );
		}

		// This call config multi-tasking of engine.
		static void _xdemo_init_multi_tasking( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Configurating multi-tasking...") );

			Ref<IData> entry = app->engine().OpenData( _S("etc/engine_config.xml") );
			// Set tasking config.
			app->engine().ConfigMultiTasking( entry );
		}

		// This callback creates input handlers, and take control of input event dispatching.
		static void _xdemo_init_input( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Preparing input...") );

			// By passing event class name, engine creates events and place them in event center.
			((InputReflection*)(app->m_inputHandler))->ConnectEngine( app->engine() );

			// Creates subscriber, and subscribe inputs, in order to receive input event.
			app->engine().SubscribeEvent(
				_S("input/keyboard") , EVENTSLOT_APP , bind( _xdemo_on_key_input , app , _1 ) );

			app->engine().SubscribeEvent(
				_S("input/mouse") , EVENTSLOT_APP , bind( _xdemo_on_mouse_input , app , _1 ) );
		}

		// This callback initializes window position and status.
		static void _xdemo_init_window( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Adjusting window...") );

			::SetWindowPos( app->m_hWnd , HWND_TOP , 0 , 0 , 0 , 0 , SWP_NOMOVE|SWP_NOREDRAW|SWP_NOSIZE );
			::SetWindowLong( app->m_hWnd , GWL_STYLE , WS_OVERLAPPEDWINDOW );
				//WS_CAPTION|WS_OVERLAPPED|WS_SYSMENU|WS_MINIMIZEBOX );

			int width = 800;
			int height = 600;

			// read settings
			Ref<IData> config = app->engine().OpenData( _S("etc/engine_config.xml") );
			if( config.IsValid() )
			{
				// create default settings
				width = config->ReadInt( _S("window/width") , 800 );
				height = config->ReadInt( _S("window/height") , 600 );
			}

			::MoveWindow( app->m_hWnd , 100 , 100 , width , height , FALSE );

			// Connect regular message as event.
			((RegularGameMessage*)app->m_regularFilter)->ConnectEngine( app->engine() );

			// Subscribe regular game messages.
			app->engine().SubscribeEvent(
				_S("message/activate") , EVENTSLOT_APP , bind( _xdemo_activate_window_call , app , _1 ) );

			app->engine().SubscribeEvent(
				_S("message/size") , EVENTSLOT_APP , bind( _xdemo_size_window_call , app , _1 ) );

			app->engine().SubscribeEvent(
				_S("message/paint") , EVENTSLOT_APP , bind( _xdemo_paint_window_call , app , _1 ) );
		}

		// This callback initializes gui manager.
		static void _xdemo_init_gui( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Building GUI...") );

			// Create GUI Manager.
			app->m_ui.Initialize( app->m_hWnd , app->m_inputDispatcher , app->engine() );
		}

		// This callback shows window while initialization is at final stage.
		static void _xdemo_init_final_window( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Showing window...") );

			// Setup overlays.
			app->engine().SetOverlaySlot( 3 );

			// bottom layer, GUI elements.
			app->engine().SetOverlayCanvas( 0 , new CCanvasProxy( bind( &UiManager::Render , app->m_ui , _1 ) ) );

			// middle layer, debug components.
			app->engine().SetOverlayCanvas( 1 , new CCanvasProxy( bind( &GameDebug::Render , app->m_debugger , _1 ) ) );

			// top layer, engine debug.
			app->engine().SetOverlayCanvas( 2 , new CCanvasProxy( bind( &EngineDebug::Render , &app->engine() , _1 ) ) );

			::ShowWindow( app->m_hWnd , SW_SHOW );
			::UpdateWindow( app->m_hWnd );
		}

		// This function creates & initializes script system.
		static void _xdemo_init_script_system( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Runing script...") );

			// Initialize python.
			PythonPlug::instance().Initialize( _S("xdemo") ,
				_S("script") , _S("script/lib;script/;script/common") , app->m_res );

			PythonPlug::instance().RedirStdio();
			PythonPlug::instance().SetEncoding( "mbcs" );

			expose_basic_to_python();
			expose_database_to_python( &app->engine() );
			expose_graphics_to_python();
			expose_scene_to_python();
			expose_ui_to_python( app->m_ui.GetDesktop() , &app->engine() );
			expose_engine_to_python();

			// Run script initialization.
			try
			{
				boost::python::object module = boost::python::import( "xeres_init" );
				module.attr("init")();
			}
			catch( boost::python::error_already_set )
			{
				PyErr_Print();
				PyErr_Clear();
				FATAL_ABORT( _S("Script initialization failed (script error)...") );
			}

			// Initialize Lua.
			LuaPlug::instance().Initialize( _S("xdemo") , app->m_res );

			expose_basic_to_lua();
		}

		static void _xdemo_active_console( XDemoApp * app , ShellConsole * console , const wchar_t * interpreter )
		{
			if( console->GetActiveInterpreter() != interpreter )
			{
				console->ActiveInterpreter( interpreter );
			}
			else
			{
				app->m_debugger->ActiveConsole( _S("ShellConsole") );
				return;
			}
			if( app->m_debugger->GetActiveConsole() != console )
				app->m_debugger->ActiveConsole( _S("ShellConsole") );
		}

		// This function creates script console
		static void _xdemo_init_console( XDemoApp * app )
		{
			TRACE_INFO( _S(".Init: Starting console...") );

			// Initialize debugger.
			app->m_debugger->Initialize();


			// Create python console.
			ShellConsole * shell_console = new ShellConsole;
			shell_console->Initialize( app->engine() );

			Ref<CPythonInterpreter> python_interp = new CPythonInterpreter;
			shell_console->AddInterpreter( _S("python") , python_interp );

			// Create lua console.
			Ref<CLuaInterpreter> lua_interp = new CLuaInterpreter;
			shell_console->AddInterpreter( _S("lua") , lua_interp );

			// Register console.
			app->m_debugger->RegisterConsole( _S("ShellConsole") , shell_console );

			// Register key map.
			app->m_hotkey.SetKey( Input::INPUT_STATE_CTRL , KeyInput::KEY_P ,
				bind( &_xdemo_active_console , app , shell_console , _S("python") ) );

			app->m_hotkey.SetKey( Input::INPUT_STATE_CTRL , KeyInput::KEY_L ,
				bind( &_xdemo_active_console , app , shell_console , _S("lua") ) );


			// Create outline console.
			Ref<IData> root_entry = app->engine().OpenData( _S(".") );

			OutlineConsole * outline_console = new OutlineConsole;
			outline_console->Initialize( app->engine() , root_entry );

			app->m_debugger->RegisterConsole( _S("OutlineConsole") , outline_console );

			// Register key map.
			app->m_hotkey.SetKey( Input::INPUT_STATE_CTRL , KeyInput::KEY_E ,
				bind( &GameDebug::ActiveConsole , app->m_debugger , _S("OutlineConsole") ) );


			// Create profile console.
			ProfileConsole * profile_console = new ProfileConsole;
			profile_console->Initialize( NULL );

			app->m_debugger->RegisterConsole( _S("ProfileConsole") , profile_console );

			// Register key map.
			app->m_hotkey.SetKey( Input::INPUT_STATE_CTRL , KeyInput::KEY_F ,
				bind( &GameDebug::ActiveConsole , app->m_debugger , _S("ProfileConsole") ) );
		}

		// finalize script
		static void _xdemo_fini_script_system( XDemoApp * app )
		{
			// finalize python
			PythonPlug::instance().Finalize();
			LuaPlug::instance().Finalize();
		}

		// finalize console
		static void _xdemo_fini_console( XDemoApp * app )
		{
			// finalize console
			app->m_debugger->Finalize();
		}

		// finalize gui manager
		static void _xdemo_fini_gui( XDemoApp * app )
		{
			// finalize python
			app->m_ui.Finalize();
		}

		// This function handles key input event.
		static HandleEvent _xdemo_on_key_input( XDemoApp * app , void * input )
		{
			KeyInput * key = static_cast<KeyInput*>(input);

			// Hack of closing short cut.
			if( key->GetKey() == KeyInput::KEY_F4 && key->IsAltDown() )
			{
				// Should force to quit.
				::PostMessage( app->m_hWnd , WM_CLOSE , 0 , 0 );
				return EVENT_HANDLED;
			}

			if( app->m_hotkey.OnKeyInput( *key ) == INPUT_HANDLED )
				return EVENT_HANDLED;

			if( app->m_debugger->OnKeyInput( *key ) == INPUT_HANDLED )
				return EVENT_HANDLED;

			if( app->m_ui.OnKeyInput( *key ) == INPUT_HANDLED )
				return EVENT_HANDLED;

			return EVENT_UNHANDLED;
		};

		// This function handles mouse input event.
		static HandleEvent _xdemo_on_mouse_input( XDemoApp * app , void * input )
		{
			MouseInput * mouse = static_cast<MouseInput*>(input);

			if( app->m_debugger->OnMouseInput( *mouse ) == INPUT_HANDLED )
				return EVENT_HANDLED;

			if( app->m_ui.OnMouseInput( *mouse ) == INPUT_HANDLED )
				return EVENT_HANDLED;

			return EVENT_UNHANDLED;
		};

		// Activate window callback.
		static HandleEvent _xdemo_activate_window_call( XDemoApp * app , void * msg )
		{
			WindowEventInfo * info = static_cast<WindowEventInfo*>(msg);
			if( info->m_tag == WINDOW_EVENT_ACTIVATE )
			{
				app->engine().SetFrameLimit( 0 );
			}
			else if( info->m_tag == WINDOW_EVENT_INACTIVATE )
			{
				app->engine().SetFrameLimit( 1 );
			}
			else
			{
				return EVENT_UNHANDLED;
			}
			return EVENT_HANDLED;
		}

		// Size change callback.
		static HandleEvent _xdemo_size_window_call( XDemoApp * app , void * msg )
		{
			WindowEventInfo * info = static_cast<WindowEventInfo*>(msg);
			static bool is_sizing = false;

			if( info->m_tag == WINDOW_EVENT_ENTERSIZING )
			{
				app->engine().DeviceOnEnterSizing( app->m_hWnd );

				is_sizing = true;
			}
			else if( info->m_tag == WINDOW_EVENT_EXITSIZING )
			{
				app->engine().DeviceOnExitSizing( app->m_hWnd );

				// UI resize.
				app->m_ui.OnSize( app->engine() , Pair<int>( info->m_rect.width() , info->m_rect.height() ) );

				is_sizing = false;
			}
			else if( info->m_tag == WINDOW_EVENT_MINIMIZED )
			{
				app->engine().DeviceOnSize( app->m_hWnd , true );
			}
			else // WINDOW_EVENT_NONE
			{
				app->engine().DeviceOnSize( app->m_hWnd , false );

				// UI resize.
				if( !is_sizing )
					app->m_ui.OnSize( app->engine() , Pair<int>( info->m_rect.width() , info->m_rect.height() ) );
			}
			return EVENT_HANDLED;
		}

		// Paint callback.
		static HandleEvent _xdemo_paint_window_call( XDemoApp * app , void * msg )
		{
			::ValidateRect( app->m_hWnd , NULL );
			return EVENT_UNHANDLED;
		}

		// Main game logic control.
		static void _xdemo_main_logic( void * context , const Frame& frame )
		{
			//COUNTER_GUARD( _S("xdemo_main") );
		}

		// UI update tick.
		static void _xdemo_update_ui( XDemoApp * app , const Frame& frame )
		{
			app->m_ui.Tick( frame );
		}

		// Main game logic update.
		static void _xdemo_update_logic( XDemoApp * app , const Frame& frame )
		{
			//COUNTER_GUARD( _S("xdemo_logic") );

// 			static float time = 0;
// 			time += frame.dtime();
// 			float stime = fmod( time , 2 );
// 			FLOAT fAngle = stime * (2.0f * (float)D3DX_PI) / 2.0f;
// 			D3DXMATRIXA16 matWorld;
// 			xeres::dx9::D3DXMatrixRotationY( &matWorld, fAngle );
// 
// 			D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
// 			D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
// 			D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
// 			D3DXMATRIXA16 matView;
// 			xeres::dx9::D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
// 
// 			D3DXMATRIXA16 matProj;
// 			xeres::dx9::D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f );

// 			app->m_camera->SetWorldMatrix( *reinterpret_cast<const Matrix<float>*>(&matWorld) );
// 			app->m_camera->SetViewMatrix( *reinterpret_cast<const Matrix<float>*>(&matView) );
// 			app->m_camera->SetProjMatrix( *reinterpret_cast<const Matrix<float>*>(&matProj) );
		}

		// Main scene creation.
		static void _xdemo_main_scene( XDemoApp * app )
		{
			// FIXME: move this section to script
			// load world
			TRACE_INFO( _S("Loading initial scene...") );

			app->m_camera = new CCameraBase;
			app->engine().SetCamera( app->m_camera );
			app->engine().InstantScene( _S("simple") );
		}
	};

	// Application initialization.
	// This function will be called before the first frame engine runs.
	// Place most initialization in this function.
	bool XDemoApp::Initialize( void )
	{
		// Create objects.
		m_inputHandler = new InputReflection;
		// 2 ways of handling input.
#if 0
		DirectInputAdapter * dinput = new DirectInputAdapter( m_inputHandler );
		m_inputTicker = dinput;
		m_inputFilter = dinput;
#else
		DefaultInputAdaptor * adaptor = new DefaultInputAdaptor( m_inputHandler );
		m_inputFilter = adaptor;
		m_inputDispatcher = adaptor;
#endif
		m_regularFilter = new RegularGameMessage;
		m_debugger = new GameDebug;

		// User should follow this way to configurate engine.

		// ----Initialization----

		// 1. Register classes.
		engine().QueueInitializer( _S("./register/xdemo") , _S("class") ,
			XDemoInitHelper::_xdemo_init_reg_class );

		// 2. Create filsystem for input/output.
		engine().QueueInitializer( _S("./filesystem") , _S("register") ,
			bind( XDemoInitHelper::_xdemo_init_filesystem , this ) );

		// 3. Initialize window position & style.
		engine().QueueInitializer( _S("./window") , _S("filesystem") ,
			bind( XDemoInitHelper::_xdemo_init_window , this ) );

		// 4. Initialize device.
		engine().QueueInitializer( _S("./device") , _S("window") ,
			bind( XDemoInitHelper::_xdemo_init_device , this ) );

		// 5. Initialize GUI manager.
		engine().QueueInitializer( _S("./gui") , _S("device") ,
			bind( XDemoInitHelper::_xdemo_init_gui , this ) );

		// 6. Initialize scene manager.
		engine().QueueInitializer( _S("./scene") , _S("gui") ,
			bind( XDemoInitHelper::_xdemo_init_scene_mgr , this ) );

		// 7. Initialize multi-tasking.
		engine().QueueInitializer( _S("./thread") , _S("scene") ,
			bind( XDemoInitHelper::_xdemo_init_multi_tasking , this ) );

		// 8. Initialize input.
		engine().QueueInitializer( _S("./input") , _S("thread") ,
			bind( XDemoInitHelper::_xdemo_init_input , this ) );

		// 9. Initialize script system.
		engine().QueueInitializer( _S("./script") , _S("input") ,
			bind( XDemoInitHelper::_xdemo_init_script_system , this ) );

		// 10. Initialize console.
		engine().QueueInitializer( _S("./console") , _S("script") ,
			bind( XDemoInitHelper::_xdemo_init_console , this ) );

		// 11. Finally, we show window.
		engine().QueueInitializer( _S("final/show") , _S("") ,
			bind( XDemoInitHelper::_xdemo_init_final_window , this ) );

		// 12. And we should prepare initial scene
		engine().QueueInitializer( _S("final/scene") , _S("show") ,
			bind( XDemoInitHelper::_xdemo_main_scene , this ) );

		// ----Finalization----

		// 1. Finalize console.
		engine().QueueFinalizer( _S("~/console") , _S("") ,
			bind( XDemoInitHelper::_xdemo_fini_console , this ) );

		// 2. Finalize script system.
		engine().QueueFinalizer( _S("~/script") , _S("console") ,
			bind( XDemoInitHelper::_xdemo_fini_script_system , this ) );

		// 3. Finalize gui manager.
		engine().QueueFinalizer( _S("~/gui") , _S("script") ,
			bind( XDemoInitHelper::_xdemo_fini_gui , this ) );

		// ----Update----
		engine().InsertStage( _S("logic/xdemo") , _S("") ,
			bind( XDemoInitHelper::_xdemo_update_logic , this , _1 ) );

		engine().InsertStage( _S("ui/update") , _S("") ,
			bind( XDemoInitHelper::_xdemo_update_ui , this , _1 ) );

		// ----Application----

		// Add input & regular message filter to application.
		AddFilter( _S("input") , _S("") , m_inputFilter );
		AddFilter( _S("regular") , _S("input") , m_regularFilter );

		// Some input needs tick callback, just add it if necessary.
		if( m_inputTicker )
			AddTicker( _S("input") , _S("") , m_inputTicker );

		if( m_debugger )
			AddTicker( _S("debugger") , m_inputTicker ? _S("input") : _S("") , m_debugger );

		return true;
	}

	// finalize
	void XDemoApp::Finalize( void )
	{
		// Cleanup created objects
		delete m_regularFilter;
		m_regularFilter = NULL;
		delete m_inputFilter;
		m_inputFilter = NULL;
		delete m_inputHandler;
		m_inputHandler = NULL;

		delete m_inputFilter;
		m_inputFilter = NULL;

		delete m_debugger;
		m_debugger = NULL;

		m_res = NULL;
		m_etc = NULL;
		m_camera = NULL;

		TRACE_INFO( _S("Exit XDemo Application...") );
	}

} // namespace xeres
